package com.prolificinteractive.materialcalendarview;

import ohos.agp.components.AttrHelper;
import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.components.ComponentContainer;
import ohos.agp.text.Font;
import ohos.app.Context;

import com.prolificinteractive.materialcalendarview.MaterialCalendarView.ShowOtherDates;
import com.prolificinteractive.materialcalendarview.format.DayFormatter;
import com.prolificinteractive.materialcalendarview.format.WeekDayFormatter;

import org.threeten.bp.DayOfWeek;
import org.threeten.bp.LocalDate;
import org.threeten.bp.temporal.TemporalField;
import org.threeten.bp.temporal.WeekFields;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import static com.prolificinteractive.materialcalendarview.MaterialCalendarView.showOtherMonths;
import static ohos.agp.components.ComponentContainer.LayoutConfig.MATCH_CONTENT;

abstract class CalendarPagerView extends ComponentContainer implements Component.ClickedListener,
    Component.LongClickedListener, Component.EstimateSizeListener, ComponentContainer.ArrangeListener {

    protected static final int DEFAULT_DAYS_IN_WEEK = 7;
    protected static final int DEFAULT_MAX_WEEKS = 6;
    protected static final int DAY_NAMES_ROW = 1;

    private final ArrayList<WeekDayView> weekDayViews = new ArrayList<>();
    private final ArrayList<DecoratorResult> decoratorResults = new ArrayList<>();
    private final DayOfWeek firstDayOfWeek;
    protected int showOtherDates = ShowOtherDates.SHOW_DEFAULTS.value;
    private final MaterialCalendarView mcv;
    private final CalendarDay firstViewDay;
    private CalendarDay minDate = null;
    private CalendarDay maxDate = null;
    protected boolean showWeekDays;


    private final Collection<DayView> dayViews = new ArrayList<>();

    public CalendarPagerView(MaterialCalendarView view, CalendarDay firstViewDay,
                             DayOfWeek firstDayOfWeek, boolean showWeekDays, Font font) {
        super(view.getContext());
        setEstimateSizeListener(this);
        setArrangeListener(this);
        this.mcv = view;
        this.firstViewDay = firstViewDay;
        this.firstDayOfWeek = firstDayOfWeek;
        this.showWeekDays = showWeekDays;
//        setClipChildren(false);
//        setClipToPadding(false);
        if (showWeekDays) {
            buildWeekDays(resetAndGetWorkingCalendar(), font);
        }
        buildDayViews(dayViews, resetAndGetWorkingCalendar());
    }

    private void buildWeekDays(LocalDate calendar, Font font) {
        LocalDate local = calendar;
        for (int i = 0; i < DEFAULT_DAYS_IN_WEEK; i++) {
            WeekDayView weekDayView = new WeekDayView(getContext(), local.getDayOfWeek(), font);
            weekDayViews.add(weekDayView);
            addComponent(weekDayView);
            local = local.plusDays(1);
        }
    }

    protected void addDayView(Collection<DayView> dayViews, LocalDate temp) {
        CalendarDay day = CalendarDay.from(temp);
        DayView dayView = new DayView(getContext(),this.mcv, day);

        dayView.setClickedListener(this);
        dayView.setLongClickedListener(this);
        dayViews.add(dayView);
        addComponent(dayView);
    }

    protected LocalDate resetAndGetWorkingCalendar() {
        final TemporalField firstDayOfWeek = WeekFields.of(this.firstDayOfWeek, 1).dayOfWeek();
        final LocalDate temp = getFirstViewDay().getDate().with(firstDayOfWeek, 1);
        int dow = temp.getDayOfWeek().getValue();
        int delta = getFirstDayOfWeek().getValue() - dow;
        //If the delta is positive, we want to remove a week
        boolean removeRow = showOtherMonths(showOtherDates) ? delta >= 0 : delta > 0;
        if (removeRow) {
            delta -= DEFAULT_DAYS_IN_WEEK;
        }
        return temp.plusDays(delta);
    }

    protected DayOfWeek getFirstDayOfWeek() {
        return firstDayOfWeek;
    }

    protected abstract void buildDayViews(Collection<DayView> dayViews, LocalDate calendar);

    protected abstract boolean isDayEnabled(CalendarDay day);

    void setDayViewDecorators(List<DecoratorResult> results, Font font) {
        this.decoratorResults.clear();
        if (results != null) {
            this.decoratorResults.addAll(results);
        }
        invalidateDecorators(font);
    }

    public void setWeekDayTextAppearance(Font taId) {
        for (WeekDayView weekDayView : weekDayViews) {
            weekDayView.setFont(taId);
        }
    }

    public void setWeekDayTextAppearanceSize(int size) {
        for (WeekDayView weekDayView : weekDayViews) {
            weekDayView.setTextSize(AttrHelper.vp2px(size, getContext()));
        }
    }

    public void setDateTextAppearance(Font taId) {
        for (DayView dayView : dayViews) {
            dayView.setFont(taId);
        }
    }

    public void setDateTextAppearanceSize(int size) {
        for (DayView dayView : dayViews) {
            dayView.setTextSize(AttrHelper.vp2px(size, getContext()));
        }
    }

    public void setShowOtherDates(int showFlags) {
        this.showOtherDates = showFlags;
        updateUi();
    }

    public void setSelectionEnabled(boolean selectionEnabled) {
        for (DayView dayView : dayViews) {
            dayView.setClickedListener(selectionEnabled ? this : null);
//            dayView.setClickable(selectionEnabled);
            dayView.setCanTouch(selectionEnabled);
        }
    }

    public void setSelectionColor(int color) {
        for (DayView dayView : dayViews) {
            dayView.setSelectionColor(color);
        }
    }

    public void setWeekDayFormatter(WeekDayFormatter formatter) {
        for (WeekDayView dayView : weekDayViews) {
            dayView.setWeekDayFormatter(formatter);
        }
    }

    public void setDayFormatter(DayFormatter formatter) {
        for (DayView dayView : dayViews) {
            dayView.setDayFormatter(formatter);
        }
    }

    public void setDayFormatterContentDescription(DayFormatter formatter) {
        for (DayView dayView : dayViews) {
            dayView.setDayFormatterContentDescription(formatter);
        }
    }

    public void setMinimumDate(CalendarDay minDate) {
        this.minDate = minDate;
        updateUi();
    }

    public void setMaximumDate(CalendarDay maxDate) {
        this.maxDate = maxDate;
        updateUi();
    }

    public void setSelectedDates(Collection<CalendarDay> dates) {
        for (DayView dayView : dayViews) {
            CalendarDay day = dayView.getDate();
            dayView.setChecked(dates != null && dates.contains(day));
        }
        invalidate();
    }

    protected void updateUi() {
        for (DayView dayView : dayViews) {
            CalendarDay day = dayView.getDate();
            dayView.setupSelection(showOtherDates, day.isInRange(minDate, maxDate), isDayEnabled(day));
        }
        invalidate();
    }

    protected void invalidateDecorators(Font font) {
        final DayViewFacade facadeAccumulator = new DayViewFacade();
        for (DayView dayView : dayViews) {
            facadeAccumulator.reset();
            for (DecoratorResult result : decoratorResults) {
                if (result.decorator.shouldDecorate(dayView.getDate())) {
                    result.result.applyTo(facadeAccumulator);
                    dayView.applyFacade(facadeAccumulator, font);
                }
            }
            dayView.applyFacade(facadeAccumulator, font);
        }
    }


    @Override
    public void onClick(Component v) {
        if (v instanceof DayView) {
            final DayView dayView = (DayView) v;
            if (mcv != null) {
                mcv.onDateClicked(dayView);
            }
        }
    }

    @Override
    public void onLongClicked(Component v) {
        if (v instanceof DayView) {
            final DayView dayView = (DayView) v;
            if (mcv != null) {
                mcv.onDateLongClicked(dayView);
            }
        }
    }

    @Override
    public boolean onEstimateSize(int widthMeasureSpec, int heightMeasureSpec) {
        final int specWidthSize = EstimateSpec.getSize(widthMeasureSpec);
        final int specWidthMode = EstimateSpec.getMode(widthMeasureSpec);
        final int specHeightSize = EstimateSpec.getSize(heightMeasureSpec);
        final int specHeightMode = EstimateSpec.getMode(heightMeasureSpec);
        //We expect to be somewhere inside a MaterialCalendarView, which should measure EXACTLY
        if (specHeightMode == EstimateSpec.UNCONSTRAINT || specWidthMode == EstimateSpec.UNCONSTRAINT) {
            throw new IllegalStateException("CalendarPagerView should never be left to decide it's size");
        }
        //The spec width should be a correct multiple
        final int measureTileWidth = specWidthSize / DEFAULT_DAYS_IN_WEEK;
        final int measureTileHeight = specHeightSize / getRows();
        //Just use the spec sizes
        setEstimatedSize(specWidthSize, specHeightSize);
        int count = getChildCount();
        for (int i = 0; i < count; i++) {
            final Component child = getComponentAt(i);
            int childWidthMeasureSpec = EstimateSpec.getSizeWithMode(
                measureTileWidth, EstimateSpec.PRECISE);
            int childHeightMeasureSpec = EstimateSpec.getSizeWithMode(
                measureTileHeight, EstimateSpec.PRECISE);
            child.estimateSize(childWidthMeasureSpec, childHeightMeasureSpec);
        }
        return true;
    }

    /**
     * Return the number of rows to display per page
     *
     * @return return
     */
    protected abstract int getRows();

    @Override
    public boolean onArrange(int left, int top, int w, int h) {
        final int parentWidth = getWidth();
        final int count = getChildCount();
        final int parentLeft = 0;
        int childTop = 0;
        int childLeft = parentLeft;
        int childRight = parentWidth;
        for (int i = 0; i < count; i++) {
            final Component child = getComponentAt(i);
            final int width = child.getEstimatedWidth();
            final int height = child.getEstimatedHeight();
            if (LocalUtils.isRTL(this)) {
                child.arrange(childRight - width, childTop, width, height);
                childRight -= width;
            } else {
                child.arrange(childLeft, childTop, width, height);
                childLeft += width;
            }
            //We should warp every so many children
            if (i % DEFAULT_DAYS_IN_WEEK == (DEFAULT_DAYS_IN_WEEK - 1)) {
                childLeft = parentLeft;
                childRight = parentWidth;
                childTop += height;
            }
        }
        return true;
    }

    @Override
    public LayoutConfig createLayoutConfig(Context context, AttrSet attrSet) {
        return new LayoutConfig(MATCH_CONTENT, MATCH_CONTENT);
    }

    protected CalendarDay getFirstViewDay() {
        return firstViewDay;
    }
}
